package com.yanyu.space.common.swagger.web;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.yanyu.space.common.core.bootlog.BootstrapLogContext;
import com.yanyu.space.common.core.constants.SpaceConstants;
import com.yanyu.space.common.core.properties.SwaggerProperties;
import com.yanyu.space.common.core.yaml.YamlPropertySourceFactory;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.*;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ClassUtils;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * swagger配置类
 *
 * @author yanyu
 */
@Configuration
@EnableSwagger2
@AllArgsConstructor
@ConditionalOnProperty(value = "space.swagger.enabled")
@EnableConfigurationProperties({SwaggerProperties.class})
@Import(SpringfoxHandlerProviderBeanPostProcessor.class)
@PropertySource(factory = YamlPropertySourceFactory.class, value = "classpath:" + SpaceConstants.Properties.SWAGGER_PROP)
@Slf4j
public class SwaggerAutoConfiguration {

    private static final String AUTHORIZATION_HEADER = "Authorization";

    private SwaggerProperties properties;

    @Bean
    public Docket docket() throws IOException {

        return new Docket(DocumentationType.SWAGGER_2)
                // 文档信息
                .apiInfo(apiInfo())
                .select()
                // 扫描的包
                .apis(basePackage(apis()))
                // 过滤的路径，所有请求
                .paths(PathSelectors.any())
                .build()
                // 认证上下文
                .securityContexts(Arrays.asList(securityContexts()))
                .securitySchemes(securitySchemes());
    }

    private SecurityContext securityContexts() {
        return SecurityContext.builder()
                .securityReferences(securityReferences())
                .forPaths(PathSelectors.any())
                .build();
    }

    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeys = new ArrayList<>();
        List<String> authHeaders = properties.getAuthHeader();
        for (String authHeader : authHeaders) {
            SecurityScheme apiKey = new ApiKey(authHeader, authHeader, "header");
            apiKeys.add(apiKey);
        }
        return apiKeys;
    }

    private List<SecurityReference> securityReferences() {
        AuthorizationScope authorizationScope = new AuthorizationScope("**", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Arrays.asList(new SecurityReference(AUTHORIZATION_HEADER, authorizationScopes));
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(properties.getTitle())
                .contact(new Contact(properties.getContact().getName(), properties.getContact().getUrl(), properties.getContact().getEmail()))
                .description(properties.getDescription())
                .version(properties.getVersion())
                .license(properties.getLicense())
                .licenseUrl(properties.getLicenseUrl())
                .build();
    }

    private String apis() throws IOException {
        Set<String> backPackages = new HashSet<>();
        if (StringUtils.isBlank(properties.getBasePackage())) {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(SpaceConstants.CorePackage.SPACE_CONTROLLER) + "**/*Controller.class";
            PathMatchingResourcePatternResolver pa = new PathMatchingResourcePatternResolver();
            Resource[] resources = pa.getResources(packageSearchPath);
            backPackages = Stream.of(resources).map(item -> {
                String prefix = StringUtils.substringBetween(item.getDescription(), "\\com\\", "\\controller\\");
                prefix = StringUtils.join("com.", StringUtils.replaceChars(prefix, "\\", "."), ".controller");
                return prefix;
            }).collect(Collectors.toSet());
        } else {
            backPackages.add(properties.getBasePackage());
        }
        BootstrapLogContext.put("swagger", String.format("开启swagger配置，扫描路径 -> %s", backPackages.stream().collect(Collectors.joining(","))));
        return backPackages.stream().collect(Collectors.joining(";"));
    }

    public static Predicate<RequestHandler> basePackage(final String basePackage) {
        return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
    }

    private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
        return input -> {
            // 循环判断匹配
            for (String strPackage : basePackage.split(";")) {
                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
                if (isMatch) {
                    return true;
                }
            }
            return false;
        };
    }

    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
        return Optional.fromNullable(input.declaringClass());
    }
}
